/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contains: framebuffer driver
 *
 * Change Logs:
 * Date           Author            Notes
 * 2023-2-4       planck             Init
 */

#include <base/log.h>

#include <base/page.h>
#include <base/driver.h>
#include <base/malloc.h>
#include <base/debug.h>
#include <base/memory.h>
#include <base/ioremap.h>
#include <drvfw/framebuffer.h>

#include <drivers/gpio.h>
#include <drivers/lcd.h>

#include <regs.h>
#include <base/cache.h>

NX_U8 NX_ALIGN(0x1000) lcd_fb[LCD_WIDTH * LCD_HEIGHT * 4];

struct f133_tconlcd_reg_t *tcon;

static void f133_tconlcd_disable(void)
{
    NX_U32 val;

    val = Read32((void *)&tcon->dclk);
    val &= ~(0xf << 28);
    Write32((void *)&tcon->dclk, val);

    Write32((void *)&tcon->gctrl, 0);
    Write32((void *)&tcon->gint0, 0);
}

static void f133_tconlcd_set_timing(void)
{
    int bp, total;
    NX_U32 val;
    val = (V_FRONT_PORCH + V_BACK_PORCH + V_SYNC_LEN) / 2;
    Write32((void *)&tcon->ctrl, (1 << 31) | (0 << 24) | (0 << 23) | ((val & 0x1f) << 4) | (0 << 0));
    val = 12;
    Write32((void *)&tcon->dclk, (0xf << 28) | ((val / 2) << 0));
    Write32((void *)&tcon->timing0, ((LCD_WIDTH - 1) << 16) | ((LCD_HEIGHT - 1) << 0));
    bp = H_SYNC_LEN + H_BACK_PORCH;
    total = LCD_WIDTH + H_FRONT_PORCH + bp;
    Write32((void *)&tcon->timing1, ((total - 1) << 16) | ((bp - 1) << 0));
    bp = V_SYNC_LEN + V_BACK_PORCH;
    total = LCD_HEIGHT + V_FRONT_PORCH + bp;
    Write32((void *)&tcon->timing2, ((total * 2) << 16) | ((bp - 1) << 0));
    Write32((void *)&tcon->timing3, ((H_SYNC_LEN - 1) << 16) | ((V_SYNC_LEN - 1) << 0));

    val = (0 << 31) | (1 << 28);
    if (!H_SYNC_ACTIVE)
        val |= (1 << 25);
    if (!V_SYNC_ACTIVE)
        val |= (1 << 24);
    if (!DEN_ACTIVE)
        val |= (1 << 27);
    if (!CLK_ACTIVE)
        val |= (1 << 26);
    Write32((void *)&tcon->io_polarity, val);
    Write32((void *)&tcon->io_tristate, 0);
}

static void f133_tconlcd_set_dither(void)
{
    if ((LCD_BITS_PER_PIXEL == 16) || (LCD_BITS_PER_PIXEL == 18))
    {
        Write32((void *)&tcon->frm_seed[0], 0x11111111);
        Write32((void *)&tcon->frm_seed[1], 0x11111111);
        Write32((void *)&tcon->frm_seed[2], 0x11111111);
        Write32((void *)&tcon->frm_seed[3], 0x11111111);
        Write32((void *)&tcon->frm_seed[4], 0x11111111);
        Write32((void *)&tcon->frm_seed[5], 0x11111111);
        Write32((void *)&tcon->frm_table[0], 0x01010000);
        Write32((void *)&tcon->frm_table[1], 0x15151111);
        Write32((void *)&tcon->frm_table[2], 0x57575555);
        Write32((void *)&tcon->frm_table[3], 0x7f7f7777);

        if (LCD_BITS_PER_PIXEL == 16)
            Write32((void *)&tcon->frm_ctrl, (1 << 31) | (1 << 6) | (0 << 5) | (1 << 4));
        if (LCD_BITS_PER_PIXEL == 18)
            Write32((void *)&tcon->frm_ctrl, (1 << 31) | (0 << 6) | (0 << 5) | (0 << 4));
    }
}

static void f133_tconlcd_enable(void)
{
    NX_U32 val;

    val = Read32((void *)&tcon->gctrl);
    val |= (1 << 31);
    Write32((void *)&tcon->gctrl, val);
}

static inline void f133_de_set_mode(void)
{
    struct de_clk_t *clk = (struct de_clk_t *)(LCD_DE_BASE_ADDR);
    struct de_glb_t *glb = (struct de_glb_t *)(LCD_DE_BASE_ADDR + F133_DE_MUX_GLB);
    struct de_bld_t *bld = (struct de_bld_t *)(LCD_DE_BASE_ADDR + F133_DE_MUX_BLD);
    struct de_ui_t *ui = (struct de_ui_t *)(LCD_DE_BASE_ADDR + F133_DE_MUX_CHAN + 0x1000 * 1);
    NX_U32 size = (((LCD_HEIGHT - 1) << 16) | (LCD_WIDTH - 1));
    NX_U32 val;
    int i;

    val = Read32((void *)&clk->rst_cfg);
    val |= 1 << 0;
    Write32((void *)&clk->rst_cfg, val);

    val = Read32((void *)&clk->gate_cfg);
    val |= 1 << 0;
    Write32((void *)&clk->gate_cfg, val);

    val = Read32((void *)&clk->bus_cfg);
    val |= 1 << 0;
    Write32((void *)&clk->bus_cfg, val);

    val = Read32((void *)&clk->sel_cfg);
    val &= ~(1 << 0);
    Write32((void *)&clk->sel_cfg, val);

    Write32((void *)&glb->ctl, (1 << 0));
    Write32((void *)&glb->status, 0);
    Write32((void *)&glb->dbuff, 1);
    Write32((void *)&glb->size, size);

    for (i = 0; i < 4; i++)
    {
        void *chan = (void *)(LCD_DE_BASE_ADDR + F133_DE_MUX_CHAN + 0x1000 * i);
        NX_MemSet(chan, 0, i == 0 ? sizeof(struct de_vi_t) : sizeof(struct de_ui_t));
    }
    NX_MemSet(bld, 0, sizeof(struct de_bld_t));

    Write32((void *)&bld->fcolor_ctl, 0x00000101);
    Write32((void *)&bld->route, 1);
    Write32((void *)&bld->premultiply, 0);
    Write32((void *)&bld->bkcolor, 0xff000000);
    Write32((void *)&bld->bld_mode[0], 0x03010301);
    Write32((void *)&bld->bld_mode[1], 0x03010301);
    Write32((void *)&bld->output_size, size);
    Write32((void *)&bld->out_ctl, 0);
    Write32((void *)&bld->ck_ctl, 0);
    for (i = 0; i < 4; i++)
    {
        Write32((void *)&bld->attr[i].fcolor, 0xff000000);
        Write32((void *)&bld->attr[i].insize, size);
    }

    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_VSU, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_GSU1, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_GSU2, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_GSU3, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_FCE, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_BWS, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_LTI, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_PEAK, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_ASE, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_FCC, 0);
    Write32(LCD_DE_BASE_ADDR + F133_DE_MUX_DCSC, 0);

    Write32((void *)&ui->cfg[0].attr, (1 << 0) | (4 << 8) | (1 << 1) | (0xff << 24));
    Write32((void *)&ui->cfg[0].size, size);
    Write32((void *)&ui->cfg[0].coord, 0);
    Write32((void *)&ui->cfg[0].pitch, 4 * LCD_WIDTH);
    Write32((void *)&ui->cfg[0].top_laddr, (NX_U32)(unsigned long)lcd_fb);
    Write32((void *)&ui->ovl_size, size);
}

static void inline f133_de_enable(void)
{
    struct de_glb_t *glb = (struct de_glb_t *)(LCD_DE_BASE_ADDR + F133_DE_MUX_GLB);
    Write32((void *)&glb->dbuff, 1);
}

static inline void f133_de_set_address(void *fb)
{
    struct de_ui_t *ui = (struct de_ui_t *)(LCD_DE_BASE_ADDR + F133_DE_MUX_CHAN + 0x1000 * 1);
    Write32((void *)&ui->cfg[0].top_laddr, (NX_U32)(unsigned long)fb);
}

static void reset_f133_deassert(void *addr, int offset)
{
    NX_U32 val;
    Write32(addr, 0x01);
    val = Read32(addr);
    val |= (1 << offset);
    val = 0x00010001;
    Write32(addr, val);
}

void lcd_gpio_config(void)
{
    // d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_0, 2);//R0
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_1, 2);  // R1
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_2, 2);  // R2
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_3, 2);  // R3
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_4, 2);  // R4
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_5, 2);  // R5
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_6, 2);  // G0
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_7, 2);  // G1
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_8, 2);  // G2
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_9, 2);  // G3
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_10, 2); // G4
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_11, 2); // G5
    // d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_12, 2);//B0
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_13, 2); // B1
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_14, 2); // B2
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_15, 2); // B3
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_16, 2); // B4
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_17, 2); // B5
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_18, 2); // LCD_CLK
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_19, 2); // LCD_DE
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_20, 2); // LCD_HSYNC
    d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_21, 2); // LCD_VSYNC

    if (LCD_BITS_PER_PIXEL == 18)
    {
        d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_0, 2);  // R0
        d1_set_gpio_mode(GPIO_PORT_D, GPIO_PIN_12, 2); // B0
    }

    NX_DcacheFlushRange((void *)LCD_TCON_BASE_ADDR, 4096 * 1024);
    NX_DcacheFlushRange((void *)LCD_DE_BASE_ADDR, 4096 * 1024);
    NX_DcacheFlushRange((void *)lcd_fb, LCD_WIDTH * LCD_HEIGHT * 4);

    reset_f133_deassert((void *)F133_DE, 16);
    reset_f133_deassert((void *)F133_TCON, 16);

    Write32((void *)0x02001600, 0x81000002);
    Write32((void *)0x02001b60, 0x81000002);
    Write32((void *)0x02001b9c, 1);

    tcon = (struct f133_tconlcd_reg_t *)((unsigned long long)LCD_TCON_BASE_ADDR);
    f133_tconlcd_disable();
    f133_tconlcd_set_timing();
    f133_tconlcd_set_dither();
    f133_tconlcd_enable();
    f133_de_set_mode();
    f133_de_enable();
    f133_de_set_address((void *)lcd_fb);
    f133_de_enable();
}

/* color : 32bit, 0x00RRGGBB
 *
 */
void fb_put_pixel(int x, int y, unsigned int color)
{
    unsigned char *pc;  /* 8bpp */
    unsigned short *pw; /* 16bpp */
    unsigned int *pdw;  /* 32bpp */

    unsigned int pixel_base = lcd_fb + (800 * 32 / 8) * y + x * 32 / 8;

    pdw = (unsigned int *)pixel_base;
    *pdw = color;
}

// #include <drivers/font_8x16.h>

// void fb_print_char(int x, int y, char c, unsigned int color)
// {
//     int i, j;

//     /* 根据c的ascii码在fontdata_8x16中得到点阵数据 */
//     unsigned char *dots = &fontdata_8x16[c * 16];

//     unsigned char data;
//     int bit;

//     /* 根据点阵来设置对应象素的颜色 */
//     for (j = y; j < y + 16; j++)
//     {
//         data = *dots++;
//         bit = 7;
//         for (i = x; i < x + 8; i++)
//         {
//             /* 根据点阵的某位决定是否描颜色 */
//             if (data & (1 << bit))
//                 fb_put_pixel(i, j, color);
//             bit--;
//         }
//     }
// }

// /* "abc\n\r123" */
// void fb_print_string(int x, int y, char *str, unsigned int color)
// {
//     int i = 0, j;

//     while (str[i])
//     {
//         if (str[i] == '\n')
//             y = y + 16;
//         else if (str[i] == '\r')
//             x = 0;

//         else
//         {
//             fb_print_char(x, y, str[i], color);
//             x = x + 8;
//             if (x >= 800) /* 换行 */
//             {
//                 x = 0;
//                 y = y + 16;
//             }
//         }
//         i++;
//     }
// }
// void show_logo(int x, int y, unsigned char *image_ptr)
// {
//     int i, j;
//     unsigned int color;

//     /* 根据点阵来设置对应象素的颜色 */
//     for (i = 0, x += 69, --y; i < 369; ++i, ++image_ptr)
//     {
//         if (*image_ptr >= 100)
//         {
//             color = (*image_ptr++ - 100) * 0xffffff;
//             ++i;
//             ++y;
//             x -= 69;
//         }
//         for (j = 0; j < *image_ptr; ++j)
//         {
//             fb_put_pixel(x++, y, color);
//         }
//         color ^= 0xffffff;
//     }
// }

// int lcd_show_logo()
// {
//     NX_Device *dev = NX_NULL;
//     NX_FramebufferInfo *info = NX_MemAlloc(sizeof(NX_FramebufferInfo));
//     if (info == NX_NULL)
//     {
//         return NX_NULL;
//     }

//     if (NX_DeviceOpen("fb0", 0, &dev) != NX_EOK)
//     {
//         NX_Printf("open fb0 failed!\n");
//         return NX_NULL;
//     }
//     NX_Printf("info = %x\n", info);

//     if (NX_DeviceControl(dev, NX_FRAMEBUFFER_CMD_GETINFO, info) != NX_EOK)
//     {
//         NX_Printf("get fb0 info failed!\n");
//         return NX_NULL;
//     }

//     NX_Printf("info = %x\n", info);
//     NX_Size sz = info->xResolution * info->yResolution * (info->bitsPerPixel / 8);

//     NX_Printf("lcd_fb = %x\n", lcd_fb);
//     NX_Printf("info  = %x\n", info);
//     NX_Printf("sz  = %x\n", sz);
//     NX_Printf("info -> phyBasePtr = %x\n", info->phyBasePtr);
//     NX_Printf("info -> xResolution = %d\n", info->xResolution);
//     NX_Printf("info -> yResolution = %d\n", info->yResolution);
//     NX_Printf("info -> bitsPerPixel = %d\n", info->bitsPerPixel);

//     NX_U32 *fbp32 = info->phyBasePtr; // NX_IoRemap(info->phyBasePtr, info->xResolution * info->yResolution * info->bitsPerPixel / 8);

//     show_logo((800 - 65) >> 1, (480 - 69) >> 1, &image);
//     show_logo(0, 0, &image);
//     show_logo(800 - 69, 0, &image);
//     show_logo(0, 480 - 69, &image);
//     show_logo(800 - 69, 480 - 69, &image);
//     fb_print_string((info->xResolution - 88) >> 1, ((info->yResolution + 69) >> 1) + 16, "NXOS Development Team", 0xffffff);
//     fb_print_string((info->xResolution - 88) >> 1, ((info->yResolution + 69) >> 1) + 32, "open source", 0xffffff);
// }
